#!/usr/bin/env bash
#####################################################################################
##
## NOTE:
## This is a command line tool to operate on otomi-core.
## All commands are executed in docker container.
## Keep this file as simple as possible:
## - do not depend on any external files.
## - do not use any non standard tooling.
## - only Docker is needed to run otomi-core image
## If you need to use any extra binaries then most probably you want to add them to the otomi/tools image.
##
#####################################################################################
# shellcheck disable=SC2128
[ "${BASH_VERSINFO:-0}" -lt 4 ] && echo "You are using $BASH_VERSINFO, while we only support Bash -ge than version 4. Please upgrade." && exit 1
calling_args="$*"

if [ -n "$TESTING" ]; then
  CI=1
  ENV_DIR="$PWD/tests/fixtures"
  NOPULL=1
  AZURE_CLIENT_ID=somevalue
  AZURE_CLIENT_SECRET=somesecret
elif [ -z "$ENV_DIR" ] && [ -n "$IN_DOCKER" ]; then
  ENV_DIR="$PWD/env"
elif [ -z "$IN_DOCKER" ] && [ -n "$ENV_DIR" ]; then
  mkdir -p $ENV_DIR
fi
[[ "$ENV_DIR" == *"../"* ]] && echo "Don't provide an ENV_DIR that contains '../'!" && exit 1
if [ -n "$CI" ]; then
  calling_args="$calling_args --non-interactive"
fi
if [[ $calling_args == 'x '* ]] && [[ $calling_args != 'x -- '* ]]; then
  calling_args="x -- ${calling_args:2}"
fi
readonly calling_args
silent() {
  if { [[ $VERBOSITY -gt 0 ]] || [[ " $calling_args" == *' -v'* ]]; } && [ -t 1 ]; then
    "$@"
  else
    "$@" &>/dev/null
  fi
}

docker_tag_exists() {
  curl -sflL https://index.docker.io/v1/repositories/$1/tags/$2 &>/dev/null
}

# shellcheck disable=SC2155
readonly base_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"

package_is_core() {
  path=$1
  { [ "$path" = "/home/app/stack" ] || [ ! -f "$path/package.json" ]; } && return 1
  packageName=$(grep name "$path/package.json" | sed 's/.*"name": "\(.*\)".*/\1/')
  [ $packageName = 'otomi-core' ] && return 0 || return 1
}

if package_is_core $(pwd); then
  in_core=1
  core_path=$(pwd)
fi

has_docker=$(command -v docker) &>/dev/null

[ -z $ENV_DIR ] && [ -n "$in_core" ] && echo "No ENV_DIR set!" && exit 1
[ -z $ENV_DIR ] && ENV_DIR="$PWD"
# Parse yaml block without yq: https://stackoverflow.com/a/49190338/14982291
fallback_otomi_version='latest'
fallback_otomi_dev_version='master'
if [ -n "$in_core" ]; then
  current_branch=$(git branch --show-current 2>/dev/null)
  current_branch=${current_branch##*/}
  current_branch=${current_branch:-$fallback_otomi_dev_version}
  otomi_version=$current_branch
elif [ -f "${ENV_DIR}/env/settings.yaml" ]; then
  otomi_version=$(awk '/otomi/{flag=1} flag && /version:/{print $NF;flag=""}' $ENV_DIR/env/settings.yaml)
  otomi_version=${otomi_version:-$fallback_otomi_version}
else
  otomi_version=$fallback_otomi_version
fi

if [ -z $NOPULL ] && [ -n "$OTOMI_TAG" ] && [ -z $CI ] && ! docker_tag_exists otomi/core $OTOMI_TAG; then
  # OTOMI_TAG was explicitly given and doesn't exists, then error out
  echo "The tag supplied '$OTOMI_TAG' does not exist as a docker image."
  exit 1
fi

otomi_version_used=${OTOMI_TAG:-$otomi_version}

if [ -z $NOPULL ] && [ -z $CI ] && ! docker_tag_exists otomi/core $otomi_version_used; then
  # When working in new branch, docker image doesn't exist before first commit/push
  # So need to fall back to working version
  [ -n "$in_core" ] && otomi_version=$fallback_otomi_dev_version || otomi_version=$fallback_otomi_version
  echo "Docker doesn't have otomi/core:${otomi_version_used}."
  echo "Falling back to otomi/core:${otomi_version}"
  otomi_version_used=$otomi_version
fi
readonly otomi_tools_image="otomi/core:${otomi_version_used}"
script_full_path="$base_dir/${BASH_SOURCE[0]##*/}"
if [[ ${BASH_SOURCE[0]} == '/'* ]]; then
  script_full_path="${BASH_SOURCE[0]}"
fi
readonly script_full_path
otomi_branch=$(curl -s https://api.github.com/repos/redkubes/otomi-core/branches | grep $otomi_version_used | sed 's/.*"name": "\(.*\)".*/\1/')
update_base="https://raw.githubusercontent.com/redkubes/otomi-core/${otomi_branch}/binzx/otomi"

date_to_format() {
  date=${1:-'1970-01-01T00:00:00Z'}
  format=${2:-+%s}
  which_date=$(which date)
  if [[ $(uname -s) == "Darwin" ]] && [[ "$which_date" == "/bin/date" ]]; then
    date -j -f "%Y-%m-%dT%H:%M:%SZ" $date $format
    return $?
  fi
  date --date="$date" $format
  return $?
}

universal_stat() {
  file=$1
  which_stat=$(which stat)
  if [[ $(uname -s) == "Darwin" ]] && [[ "$which_stat" == "/usr/bin/stat" ]]; then
    stat -f %A $file
    return $?
  fi
  stat -c '%a' $file
  return $?
}

run_self_update() {
  echo "Performing self-update..."

  # Download new version
  echo -n "Downloading latest version..."
  if ! wget --quiet --output-document="$0.tmp" $update_base; then
    echo "Failed: Error while trying to wget new version!"
    echo "File requested: $update_base"
    exit 1
  fi
  echo "Done."

  # Copy over modes from old version
  OCTAL_MODE=$(universal_stat $script_full_path)
  if ! chmod $OCTAL_MODE "$0.tmp"; then
    echo "Failed: Error while trying to set mode on $0.tmp."
    exit 1
  fi

  # Spawn update script
  cat >update-otomi.sh <<EOF
#!/bin/bash
# Overwrite old file with new
if mv "$0.tmp" "$0"; then
  echo "Done. Update complete."
  rm \$0
  exec "$0" "$calling_args"
else
  echo "Failed!"
fi
EOF

  echo -n "Inserting update process..."
  exec /bin/bash update-otomi.sh
}

check_update() {
  if [ -n "$in_core" ] ||
    [[ $calling_args == *'-non-interactive'* ]] ||
    [[ $calling_args == *'-ni'* ]]; then
    return 0
  fi
  last_commit_date=$(curl -s "https://api.github.com/repos/redkubes/otomi-core/commits?sha=${otomi_branch}&path=binzx%2Fotomi&page=1&per_page=1" | grep -A5 '"committer":' | grep '"date":' | awk -F': ' '{print $2}' | tr -d '"')
  last_file_change=$(date -u -r $script_full_path '+%Y-%m-%dT%H:%M:%SZ')
  last_commit_date_sec=$(date_to_format $last_commit_date)
  last_file_change_sec=$(date_to_format $last_file_change)

  if [ "$last_commit_date_sec" -ne "0" ] && [ "$last_commit_date_sec" -gt "$last_file_change_sec" ]; then
    read -r -p "Newer version is available, do you want to update (yes/No)? " answer
    case ${answer:0:1} in
      y | Y)
        run_self_update
        ;;
      *)
        return 0
        ;;
    esac
  fi
}
silent echo "Checking for updates"
[ -z $CI ] && [ -z $IN_DOCKER ] && check_update

tmp_env=$(mktemp)

function dump_vars() {
  for var in "$@"; do
    val="${!var}"
    [ -n "$val" ] && echo "$var=$val" >>$tmp_env
  done
}

OTOMI_PORT=${OTOMI_PORT:-17771}
OTOMI_IN_TERMINAL="false"
if [ -t 1 ]; then
  # shellcheck disable=SC2034
  OTOMI_IN_TERMINAL="true"
fi

vars=(
  AWS_ACCESS_KEY_ID
  AWS_REGION
  AWS_SECRET_ACCESS_KEY
  AZURE_CLIENT_ID
  AZURE_CLIENT_SECRET
  AZURE_TENANT_ID
  CI
  DEBUG
  ENV_DIR
  GCLOUD_SERVICE_KEY
  KUBE_VERSION_OVERRIDE
  NODE_TLS_REJECT_UNAUTHORIZED
  OTOMI_DRY_RUN
  OTOMI_IN_TERMINAL
  OTOMI_PASSWORD
  OTOMI_PORT
  OTOMI_SERVER
  OTOMI_TAG
  OTOMI_USERNAME
  PROFILE
  STATIC_COLORS
  SHELL
  TESTING
  TRACE
  VERBOSITY
  VALUES_INPUT
  VAULT_TOKEN
)
dump_vars "${vars[@]}"

cat >>$tmp_env <<EOF
OTOMI_CALLER_COMMAND=${BASH_SOURCE[0]##*/}
IN_DOCKER=1
EOF

helm_config="$HOME/.config/helm"
if [[ $(uname -s) == "Darwin" ]]; then
  helm_config="$HOME/Library/Preferences/helm"
else
  readonly linux_workaround='--user=root:root'
fi

stack_dir='/home/app/stack'
executable="node --experimental-specifier-resolution=node ${stack_dir}/dist/src/otomi.js --"

stack_volume=""
tmp_volume_dir=$(mktemp -d)
tmp_volume=" -v $tmp_volume_dir:/tmp"
if [ -n "$in_core" ]; then
  stack_dir=$core_path

  stack_volume="-v $stack_dir:$stack_dir"
  tmp_volume="-v /tmp:/tmp"

  executable="node --no-warnings --experimental-specifier-resolution=node --loader ts-node/esm ${stack_dir}/src/otomi.ts --"
  echo "OTOMI_DEV=1" >>$tmp_env
fi

check_volume_path() {
  source=$1
  dest=$2
  [ -z $source ] && [ -z $dest ] && echo "" && return 0
  echo "-v $source:$dest"
  return 0
}

cmd="${executable} $calling_args"
# If command is "otomi bash"
if [ "$1" = "bash" ] && [ "$#" = "1" ]; then
  cmd="bash"
fi

if { { [ "$otomi_version_used" = 'latest' ] || [ "$otomi_version_used" = 'master' ]; } || [ -n "$FORCE_PULL" ]; } && [ -z $NOPULL ] && [ -z $IN_DOCKER ]; then
  silent echo "Pulling latest version of the docker image, please wait"
  silent docker pull $otomi_tools_image
  status=$?
  if [ "$status" -ne 0 ]; then
    echo "Something went wrong when trying to pull '${otomi_tools_image}'"
    exit $status
  fi
fi

it='-it'
if [[ $calling_args == *'-non-interactive'* ]] ||
  [[ $calling_args == *'-ni'* ]]; then
  it=''
fi

network='--network host'
if [[ $calling_args == 'server'* ]]; then
  network="-p $OTOMI_PORT:$OTOMI_PORT"
fi

mkdir -p /tmp/otomi
container_name='otomi-core-dev-container'
count=0
[ -n "$has_docker" ] && count=$(($(docker ps -a | grep -c $container_name))) >/dev/null
container_name="$container_name-$count"

if [ -n "$IN_DOCKER" ]; then
  silent echo $cmd
  $cmd
  status=$?
elif [[ $calling_args == *'--get-yargs-completions'* ]]; then
  docker run --name $container_name --rm -e SHELL=$SHELL "$otomi_tools_image" bash -c "$cmd"
  status=$?
else
  docker run --name $container_name --rm --init $it \
    $linux_workaround \
    $network \
    $stack_volume \
    $tmp_volume \
    -v /tmp/otomi:/tmp/otomi \
    $(check_volume_path $HOME/.kube "/home/app/.kube") \
    $([ -n "$KUBECONFIG" ] && check_volume_path $KUBECONFIG "/home/app/.kube/config") \
    $(check_volume_path $helm_config "/home/app/.config/helm") \
    $(check_volume_path $HOME/.config/gcloud "/home/app/.config/gcloud") \
    $(check_volume_path $HOME/.aws "/home/app/.aws") \
    $(check_volume_path $HOME/.azure "/home/app/.azure") \
    $(check_volume_path $ENV_DIR "$stack_dir/env") \
    $(check_volume_path $ENV_DIR "$ENV_DIR") \
    $(check_volume_path /var/run/docker.sock "/var/run/docker.sock") \
    --env-file "$tmp_env" \
    -w "$stack_dir" \
    "$otomi_tools_image" \
    bash -c "$cmd"
  status=$?
fi

if [[ $calling_args == *'-skip-cleanup'* ]] || [[ $calling_args == *'-s'* ]]; then
  silent echo "keeping files"
else
  # Docker seems to be slow to release these files, needs to sleep to make sure that the file & volume are released and can be removed
  sleep 0.1
  rm -f "$tmp_env" &>/dev/null
  rm -rf "$tmp_volume_dir" &>/dev/null
fi
exit $status
